/**
 * @file GPS日志仓库
 * @description 处理GPS日志数据的数据库操作
 * @author AI Assistant
 * @date 2024-02-26
 * @updated 2024-03-09
 */

import { Injectable } from '@nestjs/common';
import { InjectModel, InjectConnection } from '@nestjs/mongoose';
import { Model, Connection, AnyBulkWriteOperation } from 'mongoose';
import { GpsLog } from '../schemas/gps-log.schema';
import { GpsLogData } from '../interfaces/gps-log-data.interface';
import { LoggerService } from '../../../common/services/logger.service';
import { DeviceListResponse } from '../interfaces/device.interface';
import { DeviceQueryDto, DeviceStatus } from '../dto/device-query.dto';
import { ApiResponse } from '../../common/dto/api-response.dto';

@Injectable()
export class GpsLogRepository {
  // 性能优化参数
  private readonly BATCH_WRITE_SIZE = 1000; // 每次批量写入的数据量
  private readonly CONCURRENT_WRITES = 5;   // 最大并发写入数
  
  constructor(
    @InjectModel(GpsLog.name) private readonly gpsLogModel: Model<GpsLog>,
    @InjectConnection() private readonly connection: Connection,
    private readonly logger: LoggerService
  ) {
    this.testConnection();
  }

  /**
   * 保存单个GPS日志数据
   * @param logData GPS日志数据
   * @returns 保存的文档
   */
  async save(logData: GpsLogData) {
    try {
      // 检查是否存在重复数据
      const existingLog = await this.gpsLogModel.findOne({
        deviceId: logData.deviceId,
        timestamp: logData.timestamp
      });

      if (existingLog) {
        this.logger.warn(
          `重复的GPS日志数据: deviceId=${logData.deviceId}, timestamp=${logData.timestamp}`,
          'GpsLogRepository'
        );
        return existingLog;
      }

      return await this.gpsLogModel.create(logData);
    } catch (error) {
      this.logger.error(
        '保存GPS日志数据失败',
        error instanceof Error ? error.stack : String(error),
        'GpsLogRepository'
      );
      throw error;
    }
  }

  /**
   * 批量保存GPS日志数据
   * @param dataList GPS日志数据列表
   */
  async bulkSave(dataList: GpsLogData[]): Promise<void> {
    if (dataList.length === 0) {
      return;
    }
    
    this.logger.log(`开始批量保存 ${dataList.length} 条GPS日志数据`, 'GpsLogRepository');
    const startTime = Date.now();
    
    try {
      // 使用upsert操作，避免手动检查重复数据
      const bulkOps: AnyBulkWriteOperation<GpsLog>[] = dataList.map(data => ({
        updateOne: {
          filter: { 
            deviceId: data.deviceId, 
            timestamp: data.timestamp 
          },
          update: { $setOnInsert: data },
          upsert: true
        }
      }));

      // 将大批量操作分割成较小的批次
      const batches: AnyBulkWriteOperation<GpsLog>[][] = [];
      for (let i = 0; i < bulkOps.length; i += this.BATCH_WRITE_SIZE) {
        batches.push(bulkOps.slice(i, i + this.BATCH_WRITE_SIZE));
      }

      // 使用Promise.all进行并发批量写入，但限制最大并发数
      const results: Array<{
        upsertedCount?: number;
        matchedCount?: number;
      }> = [];
      
      for (let i = 0; i < batches.length; i += this.CONCURRENT_WRITES) {
        const batchPromises = batches
          .slice(i, i + this.CONCURRENT_WRITES)
          .map(batch => this.gpsLogModel.bulkWrite(batch, { ordered: false }));
        
        const batchResults = await Promise.all(batchPromises);
        results.push(...batchResults);
        
        // 定期打印进度
        if ((i + this.CONCURRENT_WRITES) % (this.CONCURRENT_WRITES * 5) === 0 || i + this.CONCURRENT_WRITES >= batches.length) {
          const progress = Math.min(i + this.CONCURRENT_WRITES, batches.length) / batches.length * 100;
          this.logger.log(`批量保存进度: ${progress.toFixed(1)}%`, 'GpsLogRepository');
        }
      }

      // 计算插入和更新总数
      const totalInserted = results.reduce((sum, result) => sum + (result.upsertedCount || 0), 0);
      const totalMatched = results.reduce((sum, result) => sum + (result.matchedCount || 0), 0);

      const elapsedTime = (Date.now() - startTime) / 1000;
      this.logger.log(
        `批量保存完成: 新增 ${totalInserted} 条，重复 ${totalMatched} 条，总处理 ${dataList.length} 条，用时 ${elapsedTime.toFixed(2)}秒`,
        'GpsLogRepository'
      );
    } catch (error) {
      this.logger.error('批量保存GPS日志数据失败', error instanceof Error ? error.stack : String(error), 'GpsLogRepository');
      throw error;
    }
  }

  /**
   * 根据设备ID查询GPS日志数据
   * @param deviceId 设备ID
   * @param filters 过滤条件
   * @returns GPS日志数据列表
   */
  async findByDeviceId(deviceId: string, filters?: {
    startTime?: Date;
    endTime?: Date;
  }): Promise<GpsLog[]> {
    try {
      const query = this.buildBaseQuery(deviceId, filters);
      return this.gpsLogModel.find(query)
        .sort({ timestamp: 1 }) // 按时间正序排列
        .exec();
    } catch (error) {
      this.logger.error(
        `查询设备${deviceId}的GPS日志数据失败`,
        error instanceof Error ? error.stack : String(error),
        'GpsLogRepository'
      );
      throw error;
    }
  }

  /**
   * 分页查询设备的GPS日志数据
   * @param deviceId 设备ID
   * @param page 页码
   * @param limit 每页条数
   * @param filters 过滤条件
   * @returns 分页结果
   */
  async findByDeviceIdPaged(
    deviceId: string,
    page: number = 1,
    limit: number = 100,
    filters?: {
      startTime?: Date;
      endTime?: Date;
    }
  ) {
    try {
      const query = this.buildBaseQuery(deviceId, filters);
      
      const [data, total] = await Promise.all([
        this.gpsLogModel.find(query)
          .sort({ timestamp: 1 })
          .skip((page - 1) * limit)
          .limit(limit)
          .exec(),
        this.gpsLogModel.countDocuments(query)
      ]);

      return {
        total,
        page,
        totalPages: Math.ceil(total / limit),
        data
      };
    } catch (error) {
      this.logger.error(
        `分页查询设备${deviceId}的GPS日志数据失败`,
        error instanceof Error ? error.stack : String(error),
        'GpsLogRepository'
      );
      throw error;
    }
  }

  /**
   * 获取设备轨迹，强制按时间戳排序
   * @param deviceId 设备ID
   * @param filters 过滤条件
   * @returns 设备轨迹数据
   */
  async getDeviceTrack(deviceId: string, filters?: {
    startTime?: Date;
    endTime?: Date;
  }): Promise<GpsLog[]> {
    try {
      const query = this.buildBaseQuery(deviceId, filters);
      return this.gpsLogModel.find(query)
        .sort({ timestamp: 1 }) // 强制按时间戳升序排列
        .lean() // 返回纯JavaScript对象，而不是Mongoose文档
        .exec();
    } catch (error) {
      this.logger.error(
        `获取设备${deviceId}的轨迹数据失败`,
        error instanceof Error ? error.stack : String(error),
        'GpsLogRepository'
      );
      throw error;
    }
  }

  /**
   * 分页获取设备轨迹，强制按时间戳排序
   * @param deviceId 设备ID
   * @param page 页码
   * @param limit 每页条数
   * @param filters 过滤条件
   * @returns 分页数据
   */
  async getDeviceTrackPaged(
    deviceId: string,
    page: number = 1,
    limit: number = 100,
    filters?: {
      startTime?: Date;
      endTime?: Date;
    }
  ) {
    try {
      const query = this.buildBaseQuery(deviceId, filters);
      
      const [data, total] = await Promise.all([
        this.gpsLogModel.find(query)
          .sort({ timestamp: 1 }) // 强制按时间戳升序排列
          .skip((page - 1) * limit)
          .limit(limit)
          .lean() // 返回纯JavaScript对象，而不是Mongoose文档
          .exec(),
        this.gpsLogModel.countDocuments(query)
      ]);

      return {
        total,
        page,
        totalPages: Math.ceil(total / limit),
        data
      };
    } catch (error) {
      this.logger.error(
        `分页获取设备${deviceId}的轨迹数据失败`,
        error instanceof Error ? error.stack : String(error),
        'GpsLogRepository'
      );
      throw error;
    }
  }

  /**
   * 构建基础查询条件
   * @param deviceId 设备ID
   * @param filters 过滤条件
   * @returns 查询条件
   */
  private buildBaseQuery(deviceId: string, filters?: {
    startTime?: Date;
    endTime?: Date;
  }): any {
    const query: any = { deviceId };

    if (filters?.startTime || filters?.endTime) {
      query.timestamp = {};
      if (filters.startTime) query.timestamp.$gte = filters.startTime;
      if (filters.endTime) query.timestamp.$lte = filters.endTime;
    }

    return query;
  }

  /**
   * 测试数据库连接
   * @description 在仓库初始化时验证与MongoDB的连接是否正常
   * @returns void
   * @private
   */
  private async testConnection() {
    try {
      await this.gpsLogModel.db.collection('gps_parsed_records').findOne({});
      this.logger.log('MongoDB连接成功', 'GpsLogRepository');
    } catch (error) {
      this.logger.error(
        'MongoDB连接失败',
        error instanceof Error ? error.stack : String(error),
        'GpsLogRepository'
      );
    }
  }

  
  /**
   * 获取所有设备ID列表
   * @param queryDto 查询参数
   * @returns 设备ID列表
   */
  async getAllDeviceIdsList(queryDto: DeviceQueryDto): Promise<ApiResponse<DeviceListResponse>> {
    try {
      // 解构查询参数，status默认为全部(-1)
      const { status = DeviceStatus.ALL, keyword, page = 1, limit = 100 } = queryDto;
      
      // 添加调试日志
      this.logger.log(`获取设备列表参数: status=${status}, keyword=${keyword}, page=${page}, limit=${limit}`, 'GpsLogRepository');

      // 聚合管道 - 用于MongoDB聚合查询
      const pipeline: any[] = [];
      
      // 如果有关键字搜索，添加过滤条件
      // 支持模糊匹配设备ID
      if (keyword) {
        pipeline.push({
          $match: {
            deviceId: { $regex: keyword, $options: 'i' }
          }
        });
      }
      
      // 如果指定了日期，添加日期过滤（移到分组前，确保数据过滤正确）
      if (queryDto.date) {
        const startDate = new Date(queryDto.date);
        const endDate = new Date(startDate);
        endDate.setDate(startDate.getDate() + 1); // 设置为次日
        pipeline.push({
          $match: {
            timestamp: { $gte: startDate, $lt: endDate }
          }
        });
      }
      
      // 按设备ID分组，获取最新时间戳
      pipeline.push({
        $group: {
          _id: '$deviceId',
          lastUpdate: { $max: '$timestamp' }
        }
      });

      // 添加online字段，判断设备是否在线
      // 使用5分钟作为判断阈值
      const onlineThreshold = new Date(Date.now() - 5 * 60 * 1000); // 5分钟
      this.logger.log(`在线判断阈值: ${onlineThreshold.toISOString()}, 5分钟内有数据上报的设备视为在线`, 'GpsLogRepository');
      
      pipeline.push({
        $addFields: {
          isOnline: {
            $cond: {
              if: { $gt: ['$lastUpdate', onlineThreshold] },
              then: 1,
              else: 0
            }
          }
        }
      });
      
      // 创建一个管道副本用于获取总记录数
      const countPipeline = [...pipeline];

      // 根据状态过滤(如果指定)
      if (status !== undefined && status !== -1) {
        pipeline.push({
          $match: { isOnline: Number(status) }
        });
        countPipeline.push({
          $match: { isOnline: Number(status) }
        });
      }
      
      // 只返回设备ID和状态
      pipeline.push({
        $project: {
          _id: 0,
          deviceId: '$_id',
          status: '$isOnline'
        }
      });
      
      // 获取满足条件的设备总数
      countPipeline.push({ $count: 'total' });
      
      // 添加分页处理
      const skip = (page - 1) * limit; // 计算跳过的文档数
      pipeline.push({ $skip: skip });
      pipeline.push({ $limit: limit }); // 限制返回的文档数

      // 执行聚合查询和计数查询
      const [result, countResult] = await Promise.all([
        this.gpsLogModel.aggregate(pipeline).exec(),
        this.gpsLogModel.aggregate(countPipeline).exec()
      ]);
      
      // 获取总记录数
      const total = countResult.length > 0 ? countResult[0].total : 0;

      // 添加调试日志
      this.logger.log(`查询结果: 找到${total}个设备，当前页${result.length}个`, 'GpsLogRepository');
      if (result.length > 0) {
        this.logger.log(`第一个设备: ${JSON.stringify(result[0])}`, 'GpsLogRepository');
      }

      // 返回设备ID列表和分页信息
      return ApiResponse.success({
        total,
        page,
        limit,
        devices: result,
      }, '获取设备ID列表成功');
    } catch (error) {
      this.logger.error(
        '获取设备ID列表失败',
        error instanceof Error ? error.stack : String(error),
        'GpsLogRepository'
      );
      throw error;
    }
  }
} 